package org.wx.zk.lock;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * @description: 分布式锁的实现
 * @author: wangxun
 * @createDate: 2021/3/24
 * @version: 1.0
 */
@Slf4j
@Data
public class LockWatch implements Watcher, AsyncCallback.ChildrenCallback, AsyncCallback.Create2Callback {
    private ZooKeeper zk;
    private String ThreadName;
    private String LockName;
    private CountDownLatch cdl = new CountDownLatch(1);

    /**
     * 获取锁
     */
    public void tryLock(){
        /*
        * 使用不安全访问模式
        * 创建临时序列化节点
         */

        zk.create("/lock",getThreadName().getBytes(StandardCharsets.UTF_8),ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.EPHEMERAL_SEQUENTIAL,this,"createNode");
        try {
            cdl.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放锁
     */
    public void unLock(){
        try {
            zk.delete("/"+getLockName(),-1);
            log.info("删除节点:{},释放锁",getLockName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取当前是谁持有锁
     * @return
     * @throws KeeperException
     * @throws InterruptedException
     */
    public String getRootData() throws KeeperException, InterruptedException {
        final byte[] data = zk.getData("/", false, new Stat());
        return new String(data);
    }


    /**
     * 获取所有的子节点
     * 用于判断自己是不是要获得锁了
     */
    private void getChildren(){
        zk.getChildren("/", this, this, "getChildren");
    }

    /**
     * watch 监控的回调
     * @param event
     */
    @Override
    public void process(WatchedEvent event) {
        final Event.EventType type = event.getType();
        switch (type) {
            case None:
                break;
            case NodeCreated:
                break;
            case NodeDeleted:
                log.info("{} 节点被删除了",event.getPath());
                getChildren();
                break;
            case NodeDataChanged:
                break;
            case NodeChildrenChanged:
                break;
            case DataWatchRemoved:
                break;
            case ChildWatchRemoved:
                break;
            default:
                break;
        }
    }

    /**
     * 获取Children 的回调
     * @param rc
     * @param path
     * @param ctx
     * @param children
     */
    @Override
    public void processResult(int rc, String path, Object ctx, List<String> children) {
        if(null == children || children.size() <=0){
            return;
        }
        // 让节点 按名字从小到打排序
        Collections.sort(children);

        // 根据索名 获取对应的下标
        final int index = children.indexOf(getLockName());

        if(index == 0 ){
            // 当前就是第一个了，那么就获得锁，将自己线程的名字写到父节点上
            try {
                zk.setData("/", getThreadName().getBytes(StandardCharsets.UTF_8), -1);
                // 获得锁，执行业务代码
                cdl.countDown();
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{

            try {
                // 不是第一个，那么就watch 前节点
                log.info("{} 没有获得锁，那么就watch {} 节点",getThreadName(),children.get(index-1));
                zk.exists("/"+children.get(index-1),this);
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * create 的回调
     * @param rc
     * @param path
     * @param ctx
     * @param name
     * @param stat
     */
    @Override
    public void processResult(int rc, String path, Object ctx, String name, Stat stat) {
        if(null != stat){

            // 创建锁成功
            // 将 /lock0000XXXXX 的 / 去除 保存在LockName中
            setLockName(name.substring(1));
            log.info("{} 创建节点成功。节点名称是:{}",getThreadName(),getLockName());
            getChildren();
        }
    }
}
